package com.google.code.sysarch.v3.elves;

/**
 * # Creates a game window with specified dimensions
 * # Manages the game loop
 * # Calls Game object for rendering and updating
 * 
 * @author Alexander Wirth
 */


import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class GameWindow extends Canvas implements Runnable {
	
	/** Bufferstrategy for Double Buffering */
	private BufferStrategy bufStrat;
	/** width window */
	private int width;
	/** height window */
	private int height;
	/** title of the window */
	private String windowTitle;
	/** background color of the frame */
	private Color background;
	/** Game object called for rendering and updating */
	private Game callback;
	/** The desired screen refresh rate */
	private long fps_timer;
	private long fps_memory;
	private long fps_calc;
	private long fps_current;
	private int frames;
	/** Indicates whether the game is running */
	private boolean running;
	/** Sets the rendering hints */
	private RenderingHints hints;
	/** Background image */
	private Sprite[] backgroundset;
	
	/** 
	 * Creates a game window with the specified width and height and desired framerate
	 */
	GameWindow(int width, int height, int fps) {
		this.width = width;
		this.height = height;
		this.fps_timer = 1000000000L/fps;
		this.running = false;
		this.background = Color.BLACK;
		this.hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT);
	}
	
	/**
	 * Initialises the window. All the settings should have been made by this time.
	 * This method is called by the startGame() method
	 */
	private void initWindow() {
		
		GraphicsDevice graphDev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
		GraphicsConfiguration graphConf = graphDev.getDefaultConfiguration();
		
		JFrame container = new JFrame(windowTitle, graphConf);
		JPanel panel = (JPanel) container.getContentPane();
		panel.setBackground(Color.BLACK);
		container.setPreferredSize(new Dimension(width, height));
		panel.setLayout(null);
		panel.add(this);
		this.setBounds(0,0,width,height);
		this.setIgnoreRepaint(true);
		container.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);		
		container.pack();
		container.setResizable(false);
		container.setVisible(true);
		setFocusable(true);
		addKeyListener(InputManager.get());
		addMouseListener(InputManager.get());
		addMouseMotionListener(InputManager.get());
		
		this.createBufferStrategy(2);
		this.bufStrat = this.getBufferStrategy();			
	}
	
	public void run() {
		gameLoop();
	}
	
	/**
	 * This method will loop while running is true.
	 * In every cycle it will call the render() and update() methods.
	 * Optimally the duration of one cycle will fit the desired framerate.
	 */
	private void gameLoop() {
		double delta;
		long wait;
		this.fps_memory = System.currentTimeMillis();
		
		while (this.running) {
			delta = (double)(System.nanoTime() - fps_memory) / 1000000000L;
			fps_memory = System.nanoTime();		
			
			this.requestFocus();
			update(delta);
			render();
						
			wait = fps_timer - (System.nanoTime() - fps_memory);									
			wait = wait/1000000;			
			if (wait > 0) {
				try {
					Thread.sleep(wait); 
				} catch (InterruptedException e){ }
			}
			
		}
		System.exit(0);
	}
	
	/**
	 * Inits the drawing surface
	 * Calls render() from the callback
	 */
	private void render() {
		Graphics2D g = null;
				
		try {
			g = (Graphics2D) this.bufStrat.getDrawGraphics();
			g.setRenderingHints(hints);
			
			if (backgroundset != null) {
				for (int i=0; i<backgroundset.length; i++) {
					backgroundset[i].draw(g, (i%16)*64, (i/16)*64);
				}
			} else {
				g.setColor(background);
				g.fillRect(0,0,width,height);
			}
			
			callback.render(g);
			
			g.setColor(Color.WHITE);
			g.drawString("FPS: "+getFramesPerSecond(), width-70, 20);

			this.bufStrat.show();
			
		} finally {
			g.dispose();
		}
	}		
	
	/**
	 * Call the update() method of the callback
	 * @param delta Time in seconds that passed since the last call of this method
	 */
	private void update(double delta) {
		callback.update(delta);
	}

	public Color getBackground() {
		return background;
	}

	public void setBackground(Color background) {
		this.background = background;
	}
	
	public void setBackground(Sprite[] bgSet) {
		this.backgroundset = bgSet;
	}	

	public Game getCallback() {
		return callback;
	}

	public void setCallback(Game callback) {
		this.callback = callback;
	}

	public String getWindowTitle() {
		return windowTitle;
	}

	public void setWindowTitle(String windowTitle) {
		this.windowTitle = windowTitle;
	}	
	
	public int getFps() {
		return (int)(1000000000L/fps_timer);
	}

	public void setFps_timer(int fps) {
		this.fps_timer = 1000000000L/fps;
	}
	
	/** 
	 * Calculates the PFS.
	 * Must be called in every rendering loop, to work correct 
	 * @return the calculated FPS
	 */
	private long getFramesPerSecond() {
		this.frames++;
		if (System.nanoTime() - this.fps_calc >= 1000000000) {
			this.fps_current = this.frames;
			this.frames = 0;
			this.fps_calc = System.nanoTime();
		}
		return fps_current;
	}	
	
	/** Sets the hints for rendering */
	public void setRenderingHints(RenderingHints hints) {
		this.hints.putAll(hints);
	}
	
	/** Enables or disables antialiasing */
	public void antialias(boolean on) {
		hints.remove(RenderingHints.KEY_ANTIALIASING);
		if (on) {
			hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		} else {
			hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
		}
	}
	
	public void startGame() {
		initWindow();
		this.running = true;
		Thread t = new Thread(this);
		t.setPriority(Thread.MAX_PRIORITY);
		t.start();
		//gameLoop();
	}
	
	public void quitGame() {
		this.running = false;
	}
	
}
